/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package generator

import java.io.PrintWriter
import java.io.StringWriter
import com.google.protobuf.Descriptors.Descriptor
import parser.Nodes._
import parser.DataTypes._
import parser.Results._

/**
 * Converts expression to Java code.
 */
class ExprConverter(tc: TypeMapper, msgs: List[Descriptor], enclosedVars: Set[Int]) {

  import Generator.localVarName
  import Generator.mkEncVarDecl
  import Generator.bool2long
  import Generator.long2bool

  private val converters = Set() ++ {
    val nameMap = tc.classMap.map { case (k, v) => (v, k) }

    msgs.map{_.getName}.filter{tc.usedClasses.contains(_)}.flatMap { name =>
      List((name,               FunctionType(nameMap(name), List(BytesType))),
           (BytesType.toString, FunctionType(BytesType, List(nameMap(name)))))
    }
  }

  // TODO: 式単独ではJavaの文として不適合なものがある。
  // 演算子がトップレベルであると、Javaの文としてはエラーになる。
  // 例:
  //  1 + 1;

  /**
   * Converts expression to java code.
   */
  def convert(e: Expr): String = e match {
    case MemAccOp(tuple, member, _) =>
      "(%s).%s".format(convert(tuple), member)

    case AssignOp(ContainerAccessOp(container, index, _), right) if (stripRef(container.dataType).isInstanceOf[MapType]) =>
      "%s.put(%s, %s)".format(
        convert(container),
        convert(index),
        convert(right))
      // TODO: 代入演算子は戻り値としてLongの値を返すことになっているが、その様な値を返すものになっていない。

    case AssignOp(ContainerAccessOp(container, index, _), right) if (stripRef(container.dataType).isInstanceOf[ArrayType]) =>
      "%s.set(%s, %s)".format(
        convert(container),
        convertIntValue(index),
        convert(right))

      // TODO: 代入演算子は戻り値としてLongの値を返すことになっているが、その様な値を返すものになっていない。

    case AssignOp(left: LocalVariable, right) if (enclosedVars.contains(left.id)) =>
      "(%s).assign(%s)".format(localVarName(left), convert(right))

    case AssignOp(left, right) =>
      "(%s) = (%s)".format(
        convert(left),
        convert(right))

    case AndOp(left, right) =>
      "((%s) != 0) && ((%s) != 0)".format(convert(left), convert(right))

    case OrOp(left, right) =>
      "((%s) != 0) || ((%s) != 0)".format(convert(left), convert(right))

    case expr: BinOp    =>
      val l = convert(expr.left)
      val r = convert(expr.right)
      expr.op match {
        case "==" => 
          if (isValueType(expr.left)) bool2long("((%s) == (%s))".format(l, r))
          else                        bool2long("((%s).equals(%s))".format(l, r))
        case "!=" => 
          if (isValueType(expr.left)) bool2long("((%s) != (%s))".format(l, r))
          else                        bool2long("(!(%s).equals(%s))".format(l, r))
        case ">" | "<" | "<=" | ">=" => bool2long("((%s) %s (%s))".format(l, expr.op, r))
        case _ => "((%s) %s (%s))".format(l, expr.op, r)
      }

    case x: UnOp => "%s %s".format(x.op, convert(x.arg))

    case ContainerAccessOp(container, index, dataType) =>
      stripRef(container.dataType) match {
        case _: ArrayType  => "(%s).get(%s)".format(convert(container), convertIntValue(index))
        case BytesType     => "((%s).byteAt(%s))".format(convert(container), convertIntValue(index))
        case _: MapType    => "(%s).get(%s)".format(convert(container), convert(index))
        case StringType    => "(%s).charAt(%s)".format(convert(container), convertIntValue(index))
        case _ => error("Not container.")
      }

    case MapExpr(items, _, _) =>
      val MapType(kt, vt) = e.dataType
      "Helpers.<%s, %s>makeMap(%s)".format(
        tc.map(kt),
        tc.map(vt),
        items.map { case (k,v) => "new Helpers.KeyValue(%s, %s)".format(convert(k), convert(v)) } .mkString(", "))

    case ArrayExpr(items, _) => 
      val ArrayType(t) = e.dataType

      "Arrays.<%s>asList(%s)".format(tc.map(t), items.map(convert).mkString(", "))

    case TupleExpr(items, members)           => 
      "new %s(%s)".format(tc.map(e.dataType), items.map(convert).mkString(", "))

    case EnumExpr(value, _) => value.toString

    case e: AnalyzedFuncLit =>
      def encVarName(a: UsedArg) = "enclosed_%d_%s".format(a.id, a.name)

      val args = e.args.zipWithIndex.map { case (x, n) =>
        val name = x match {
          case a: UnusedArg                                => "dummy_%0d"
          case a: UsedArg if (enclosedVars.contains(a.id)) => encVarName(a)
          case a: UsedArg                                  => localVarName(a)
        }
        "%s %s".format(tc.map(x.dataType), name)
      }.mkString(", ")

      val encVars = e.args.flatMap {
        case a: UsedArg if (enclosedVars.contains(a.id)) => 
          List(mkEncVarDecl(tc.map(a.dataType), localVarName(a), encVarName(a)))
        case _ => Nil
      }

      val sw = new StringWriter()
      val pw = new PrintWriter(sw)

      StmtPrinter.print(pw, tc, this, enclosedVars, e.stmts)
      pw.flush

      "new %s() {\n".format(tc.map(e.dataType)) +
      "@Override public %s invoke(%s) {\n".format(tc.map(e.ret), args) +
      encVars.mkString("\n") +
      sw.toString +
      "}\n" +
      "}"

    case BuildInFunction(name, ftype) =>
      val args = ftype.argsType.zipWithIndex.map { case (x, n) =>
        tc.map(x) + " x%d".format(n)
      }.mkString(", ")

      val prefix = if (converters.contains((name, ftype))) "converters"
                   else                                    "BuildIn"

      "new %s() {\n".format(tc.map(ftype)) +
      "@Override public %s invoke(%s) {\n".format(tc.map(ftype.retType), args) +
      "return %s.func_%s(%s);".format(prefix, name, (0 until ftype.argsType.length).map {"x%d".format(_)}.mkString(", ")) +
      "}\n" +
      "}"

    case StaticVariable(name, _, spos) => "statics.static_%d_%s".format(spos, name)
    case l: LocalVariable =>
      if (enclosedVars.contains(l.id)) localVarName(l) + ".get()"
      else                             localVarName(l)
    case GlobalVariable(name, _, spos) => "global_%d_%s".format(spos, name)
    case e: FuncCall               => convertFunction(e)
    case ValueExpr(expr)           => convert(expr)
    case x: IntLit                 => "%dl".format(x.value)
    case x: StringLit              => escapeStr(x.value)
    case x: FloatLit               => "%s".format(x.value)
    case x: UndefLit               => "null"
    case _: Variable => error("Unexpect expression")
    case _: FuncLit  => error("Unexpect expression")
  }

  private def convertIntValue(e: Expr): String = e match {
    case x:IntLit => "(int) " + convert(e)
    case x        => "(%s).intValue()".format(convert(e))
  }

  private def isValueType(expr: Expr) = {
    def f() = expr match {
      case _: IntLit   => true
      case _: FloatLit => true
      case _: BinOp    => true
      case _: UnOp     => true
      case _           => false
    }

    stripRef(expr.dataType) match {
      case _: EnumType => true
      case IntType     => f()
      case FloatType   => f()
      case _           => false
    }
  }

  private def convertFunction(e: FuncCall): String = {
    val FuncCall(func, args) = e

    func match {
      case BuildInFunction(name, ftype) => 
        val fname = if (name == "serialize") BytesType.name
                    else                     name

        val key = (fname, FunctionType(
          resolveType(tc.typeMap, ftype.retType),
          ftype.argsType.map{resolveType(tc.typeMap, _)}))

        val prefix = if (converters.contains(key)) "converters"
                     else                          "BuildIn"

        require(!parser.Lexer.delim_str.contains(fname))
        if (name == "serialize") {
          assert(e.args.size == 1)
          val arg = e.args(0)

          stripRef(arg.dataType) match {
            case t: ArrayType => tc.classMap.get(t) match {
              case Some(x) => "%s.func_%s(new %s(%s))".format(prefix, BytesType.name, "DataTypes." + x, convert(arg))
              case None    => "%s.func_%s(%s)".format(prefix, BytesType.name, convert(arg))
            }
            case _ => "%s.func_%s(%s)".format(prefix, BytesType.name, convert(arg))
          }
          
        } else {
          val args = e.args.map(convert)
          "%s.func_%s(%s)".format(prefix, name, args.mkString(", "))
        }
      case _ =>
        val args = e.args.map(convert)
        "(%s).invoke(%s)".format(convert(func), args.mkString(". "))
    }
  }

  /**
   * Escapes special character in specified string.
   */
  private def escapeStr(str: String) = {
    val sb = new StringBuilder()
    sb.append("\"")
    for (c <- str) {
      c match {
        case '\b' => sb.append("\\b")
        case '\t' => sb.append("\\t")
        case '\n' => sb.append("\\n")
        case '\f' => sb.append("\\f")
        case '\r' => sb.append("\\r")
        case '\"' => sb.append("\\\"")
        case '\'' => sb.append("\\\'")
        case '\\' => sb.append("\\\\")
        case x if (32 < x || x <= 127) => 
          sb.append("\\u%04x".format(x.asInstanceOf[Int]))
        case x => sb.append(x)
      }
    }
    sb.append("\"")
    sb.toString
  }
}
